﻿using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Newtonsoft.Json.Linq;
using SocialApi.Core;
using SocialApi.Core.Extensions;
using SocialApi.Core.Request;
using SocialApi.Instagram.AppSettings;
using SocialApi.Instagram.Entities.Methods.Comments;
using SocialApi.Instagram.Entities.Methods.Likes;
using SocialApi.Instagram.Entities.Methods.Locations;
using SocialApi.Instagram.Entities.Methods.Media;
using SocialApi.Instagram.Entities.Methods.Relationships;
using SocialApi.Instagram.Entities.Methods.Tags;
using SocialApi.Instagram.Entities.Methods.Users;
using SocialApi.Instagram.Exceptions;

namespace SocialApi.Instagram
{
    /// <summary>
    /// Методы для работы с API Instagram
    /// </summary>
    public class InstagramService : IParser
    {
        #region Настройки

        /// <summary>
        /// Токен доступа приложения
        /// </summary>
        public string AccessToken { get { return _accessToken; } }
        private static string _accessToken;

#if NET_4_5
        private void NavigatorOnFalied(object sender, WebException webException)
        {
            throw new IntsagramException(webException.Message);
        }
#else
        private void NavigatorOnFalied(object sender, EventArgs<WebException> webException)
        {
            throw new IntsagramException(webException.Parameter.Message);
        }
#endif
        

        internal static void CheckResult(JObject jObject)
        {
            if (((string)jObject["error"]).Exists())
            {
                throw new IntsagramApiException((string)jObject["error"], (string)jObject["error_reason"], (string)jObject["error_description"]);
            }
        }

        /// <summary>
        /// Событие возникает после успешного получения токена доступа
        /// </summary>
        public event EventHandler ParseCallbakUrlComplete;

        /// <summary>
        /// Событие возникает при неудачном получении токена доступа
        /// </summary>
        public event EventHandler ParseCallbakUrlFalied;

        #endregion

        #region Помощники

        /// <summary>
        /// Создание базовго URL из фрагментов
        /// </summary>
        /// <param name="urls">Фрагменты URL</param>
        /// <returns>Базовый URL</returns>
        internal static string GetMethodUrl(params string[] urls)
        {
            var result = new StringBuilder();
            foreach (var url in urls)
            {
                result.Append(url);
            }
            result.AppendFormat("?access_token={0}", _accessToken);
            return result.ToString();
        }

        #endregion

        /// <summary>
        /// Данные приложения
        /// </summary>
        public ClientData AppData { get; private set; }

        /// <summary>
        /// Методы для работы с пользовательским API
        /// </summary>
        public IUserMethods UserApi { get; private set; }

        /// <summary>
        /// Методы для работы с подписчиками пользователя
        /// </summary>
        public IRelationshipMethods RelationshipApi { get; private set; }

        /// <summary>
        /// Методы для работы c медиа
        /// </summary>
        public IMediaMethods MediaApi { get; private set; }

        /// <summary>
        /// Методы для работы c комментариями
        /// </summary>
        public ICommentMethods CommentApi { get; private set; }

        /// <summary>
        /// Методы для работы c лайками
        /// </summary>
        public ILikeMethods LikeApi { get; private set; }

        /// <summary>
        /// Методы для работы c отметками
        /// </summary>
        public ITagMethods TagApi { get; private set; }

        /// <summary>
        /// Методы для работы c местоположением
        /// </summary>
        public ILocationMethods LocationApi { get; private set; }

        /// <summary>
        /// URL авторизации
        /// </summary>
        public string AuthUri
        {
            get
            {
                var param = new
                {
                    client_id = AppData.CleintId,
                    redirect_uri = AppData.RedirectUrl,
                    scope = AppData.Scope.ToString(),
                    response_type = "code"
                };
                return string.Format("{0}{1}", ApiUrls.Authentication, Navigator.GetData(param));
            }
        }

        /// <summary>
        /// Создаёт новый экземпляр класса <see cref="InstagramService" />
        /// </summary>
        /// <param name="appData">Настройки приложения</param>
        /// <param name="accessToken">Токен доступа пользователя</param>
        public InstagramService(ClientData appData, string accessToken = null)
        {
            AppData = appData;
            _accessToken = accessToken;
#if NET_4_5
            Navigator.Instance.Falied += NavigatorOnFalied;
#else
            Navigator.Instance.Falied += NavigatorOnFalied;
#endif
            UserApi = new UserMethods();
            RelationshipApi = new RelationshipMethods();
            MediaApi = new MediaMethods();
            CommentApi = new CommentMethods();
            LikeApi = new LikeMethods();
            TagApi = new TagMethods();
            LocationApi = new LocationMethods();
        }

        private void SetUserData(string response)
        {
            var jObject = JObject.Parse(response);
            _accessToken = (string)jObject["access_token"];
            UserMethods.This.SetData(jObject["user"].ToObject<UserMethods>());
            if (ParseCallbakUrlComplete.Exists())
            {
                ParseCallbakUrlComplete(this, new EventArgs());
            }
        }

        #region Члены IParser

        #region Асинхронный парсинг

        /// <summary>
        /// Парсинг URL с кодом и асинхронное получение <see cref="AccessToken"/>
        /// </summary>
        /// <param name="url">URL с кодом</param>
        public async Task ParseCallbakUrlAsync(string url)
        {
            await ParseCallbakUrlAsync(new Uri(url));
        }

        /// <summary>
        /// Парсинг URL ответа сервера и асинхронное получение <see cref="AccessToken"/>
        /// </summary>
        /// <param name="url">URL ответа сервера</param>
        public async Task ParseCallbakUrlAsync(Uri url)
        {
            var callbakUrl = HttpUtility.ParseQueryString(url.Query.TrimStart('?'));
            if (callbakUrl["error"].Exists())
            {
                throw new IntsagramApiException(callbakUrl["error"], callbakUrl["error_reason"], callbakUrl["error_description"]);
            }
            if (callbakUrl["code"].Exists())
            {

                var param = new
                {
                    client_id = AppData.CleintId,
                    client_secret = AppData.ClientSecret,
                    redirect_uri = AppData.RedirectUrl,
                    grant_type = "authorization_code",
                    code = callbakUrl["code"]
                };
                var result = await Navigator.Instance.PostAsync(ApiUrls.AccessToken, param);
                SetUserData(result);
            }
        }

        #endregion

        #region Cинхронный парсинг

        /// <summary>
        /// Парсинг URL с кодом и получение <see cref="AccessToken"/>
        /// </summary>
        /// <param name="url">URL с кодом</param>
        public void ParseCallbakUrl(string url)
        {
            ParseCallbakUrl(new Uri(url));
        }

        /// <summary>
        /// Парсинг URL ответа сервера и получение <see cref="AccessToken"/>
        /// </summary>
        /// <param name="url">URL ответа сервера</param>
        public void ParseCallbakUrl(Uri url)
        {
            var callbakUrl = HttpUtility.ParseQueryString(url.Query.TrimStart('?'));
            if (callbakUrl["error"].Exists())
            {
                throw new IntsagramApiException(callbakUrl["error"], callbakUrl["error_reason"], callbakUrl["error_description"]);
            }
            if (callbakUrl["code"].Exists())
            {
                var param = new
                {
                    client_id = AppData.CleintId,
                    client_secret = AppData.ClientSecret,
                    redirect_uri = AppData.RedirectUrl,
                    grant_type = "authorization_code",
                    code = callbakUrl["code"]
                };
                SetUserData(Navigator.Instance.Post(ApiUrls.AccessToken, param));
            }
        }

        #endregion

        #endregion
    }
}